package cn.go.buster.launcher.api;

import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;

import androidx.fragment.app.Fragment;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.go.buster.ThreadMode;
import cn.go.buster.annotation.GoBus;
import cn.go.buster.event.EventCard;
import cn.go.buster.launcher.Const;
import cn.go.buster.launcher.logger.ILogger;
import cn.go.buster.launcher.logger.LogPrinter;
import cn.go.buster.launcher.utils.MethodUtils;
import dalvik.system.DexFile;

/**
 * @author logcat
 * @date 2020/07/20 16:10
 * 事件核心类
 */
public class EventCenter {

    public static volatile EventCenter instance;
    private Map<String, EventCard> eventMap;

    public static ILogger logger = new LogPrinter();

    private EventCenter() {
        eventMap = new HashMap<>();
    }

    public static EventCenter getInstance() {
        if (instance == null) {
            synchronized (EventCenter.class) {
                if (instance == null) {
                    instance = new EventCenter();
                }
            }
        }
        return instance;
    }


    /**
     * Put all the event marked by {@link GoBus}
     *
     * @param eventCard An event container.
     */
    public void put(EventCard eventCard) {

        if (eventCard.getNode().getClass().getName().equals(eventCard.getEventClassName())) {
            Method[] declaredMethods = eventCard.getNode().getClass().getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (declaredMethod.isAnnotationPresent(GoBus.class) && declaredMethod.getName().equals(eventCard.getMethodName())) {
                    if (MethodUtils.getInstance().isSameParameter(declaredMethod.getParameterTypes(), MethodUtils.getInstance().getMethodValueTypes(eventCard))) {
                        StringBuilder keyBuilder = new StringBuilder();
                        keyBuilder.append(eventCard.getTag())
                                .append("_")
                                .append(eventCard.getEventClassName());
                        eventCard.setMethod(declaredMethod);
                        eventMap.put(keyBuilder.toString() , eventCard);
                    }

                }
            }
        }

    }


    /**
     * Remove an event.
     *
     * @param eventCard
     */
    public void remove(EventCard eventCard) {
        if (eventCard.getEventClassName().equals(eventCard.getNode().getClass().getName())) {
            StringBuilder keyBuilder = new StringBuilder();
            keyBuilder.append(eventCard.getTag())
                    .append("_")
                    .append(eventCard.getEventClassName());
            eventMap.get(eventCard.getTag()).setMethod(null);
            eventMap.remove(keyBuilder.toString());
        }
    }


    /**
     * Register a node which is an Activity or a Fragment.
     *
     * @param nodeObject
     */
    public void register(Object nodeObject) {

        Context currentContext = null;
        try {
            if (Activity.class.isAssignableFrom(nodeObject.getClass())) {
                currentContext = (Context) nodeObject;
            } else if (Fragment.class.isAssignableFrom(nodeObject.getClass())) {
                currentContext = ((Fragment) nodeObject).getActivity();
            }

            List<String> classNames = getClasses(currentContext, Const.EVENT_PACKAGE_NAME);
            for (String className : classNames) {

                Class aClass = Class.forName(className);
                if (BaseGoBuster.class.isAssignableFrom(aClass)) {
                    Object obj = aClass.newInstance();
                    BaseGoBuster baseGoBuster = (BaseGoBuster) obj;
                    baseGoBuster.put(nodeObject);
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Unregister current node.
     *
     * @param nodeObject
     */
    public void unRegister(Object nodeObject) {

        try {
            Context currentContext = null;

            if (Activity.class.isAssignableFrom(nodeObject.getClass())) {
                currentContext = (Context) nodeObject;
            } else if (Fragment.class.isAssignableFrom(nodeObject.getClass())) {
                currentContext = ((Fragment) nodeObject).getActivity();
            }


            List<String> classNames = getClasses(currentContext.getApplicationContext(), Const.EVENT_PACKAGE_NAME);
            for (String className : classNames) {
                Class aClass = Class.forName(className);
                if (BaseGoBuster.class.isAssignableFrom(aClass)) {
                    Object obj = aClass.newInstance();
                    BaseGoBuster iGoBuster = (BaseGoBuster) obj;
                    iGoBuster.remove(nodeObject);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            nodeObject = null;
        }
    }


    /**
     * Get all classes from a package.
     *
     * @param context
     * @param packageName
     * @return
     */
    private static synchronized List<String> getClasses(Context context, String packageName) {

        List<String> classList = new ArrayList<>();
        String path = null;

        try {
            path = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0).sourceDir;
            DexFile dexFile = new DexFile(path);
            Enumeration entries = dexFile.entries();
            while (entries.hasMoreElements()) {
                String name = (String) entries.nextElement();
                if (name.contains(packageName)) {
                    classList.add(name);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classList;
    }


    /**
     * Post an event which tag is @param tag and args is @param args.
     *
     * @param tag
     * @param params
     * @throws Exception
     */
    public void post(String tag, Object... params)  {
        for (String key : eventMap.keySet()){
            if(!key.contains(tag)){
                continue;
            }
            post(eventMap.get(key) , params);
        }
    }


    private void post(EventCard eventCard , Object... params){
        Method method = eventCard.getMethod();
        Object obj = eventCard.getNode();
        if (obj == null) {
            throw new NullPointerException("Target declare class is null!");
        }
        if (method != null) {
            try {
                if (eventCard.getThreadMode() == ThreadMode.MAIN) {
                    MethodUtils.getInstance().runOnMainThread(method, obj, params);
                }

            } catch (Exception e) {
                e.printStackTrace();
                if (e.getMessage().contains(Const.ERROR_WRONG_ARGUMENTS)) {
                    logger.error("Arguments error , please check your params!!!");
                }
            }
        }
    }
}
